Passed
Pull Request — master (#41)
by
unknown
04:36
created

UserEventHelpers.ts ➔ subscribeToResetHighlight   A

Complexity

Conditions 3

Size

Total Lines 24
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 24
rs 9.376
c 0
b 0
f 0
cc 3
1
import {event, select} from 'd3-selection';
2
import {DependencyNode, TreeNode} from '../../components/types';
3
import {
4
    Colors,
5
    ElementIds,
6
    FAST_TRANSITION_DURATION,
7
    MAXIMUM_ZOOM_SCALE,
8
    MINIMUM_ZOOM_SCALE,
9
    TRANSITION_DURATION,
10
    ZOOM_DECREASE,
11
    ZOOM_INCREASE,
12
} from '../AppConsts';
13
import {zoom, zoomIdentity} from 'd3-zoom';
14
import {
15
    selectAllNodes,
16
    selectDetailsButtonWrapper,
17
    selectDetailsExitButtonWrapper,
18
    selectHighLightedNodes,
19
    selectOverviewContainer,
20
    selectTooltip,
21
    selectTooltipBackground,
22
    selectTooltipText,
23
} from './Selectors';
24
import {initializeDetailsView, shutdownDetailsView} from '../../details/details-container';
25
import {
26
    hideHighlightBackground,
27
    zoomToHighLightedNodes
28
} from '../../overview/highlight-background/highlight-background.helpers';
29
import {findMaxDependencyLevel, getHighLightedLabelColor} from '../../overview/graph-nodes';
30
import {centerScreenToDimension, findGroupBackgroundDimension, highlightNodes} from '../../overview/overview.helpers';
31
import {changeZoom} from '../../zoom/zoom';
32
33
enum Subscriptions {
34
    HIGHLIGHT = 'click.highlight',
35
    RESET_HIGHLIGHT = 'click.resetHighlight',
36
    CHANGE_HIGHLIGHT_RANGE = 'keydown.changeHighlightRange',
37
    ZOOM_ON_ARROW_KEY = 'keydown.zoom',
38
    OPEN_DETAILS = 'click.openDetails',
39
    CLOSE_DETAILS = 'click.closeDetails',
40
    SHOW_TOOLTIP = 'mouseover.tooltip',
41
    HIDE_TOOLTIP = 'mouseout.tooltip',
42
}
43
44
export function subscribeToHighlight() {
45
    selectAllNodes().on(Subscriptions.HIGHLIGHT, (node: DependencyNode) => {
46
        LevelStorage.reset();
47
        if (node.links.length) {
48
            highlightNodes(node);
49
            zoomToHighLightedNodes();
50
        }
51
        event.stopPropagation();
52
    });
53
}
54
55
export function subscribeToChangeHighlightRangeOnArrowKey() {
56
    select('body').on(Subscriptions.CHANGE_HIGHLIGHT_RANGE, () => {
57
        if (event.code === 'ArrowRight' || event.code === 'ArrowLeft') {
58
            const labelNodesGroup = select<SVGGElement, DependencyNode>('g#labels');
59
            LevelStorage.setMaxLevel(findMaxDependencyLevel(labelNodesGroup));
60
61
            if (!isFinite(LevelStorage.getMaxLevel())) {
62
                return;
63
            }
64
65
            if (LevelStorage.isBelowMax() && event.code === 'ArrowRight') {
66
                LevelStorage.increase();
67
            }
68
69
            if (LevelStorage.isAboveMin() && event.code === 'ArrowLeft') {
70
                LevelStorage.decrease();
71
            }
72
73
            // TODO refactor it to share logic with GraphHelpers/highlight function
74
            labelNodesGroup
75
                .selectAll<HTMLElement, DependencyNode>('g')
76
                .filter((node: DependencyNode) => node.level > 0)
77
                .each(function(this: HTMLElement, node: DependencyNode) {
78
                    const labelElement = this.firstElementChild;
79
                    const textElement = this.lastElementChild;
80
81
                    if (!labelElement || !textElement) {
82
                        return;
83
                    }
84
85
                    let labelColor = Colors.LIGHT_GREY;
86
                    let textColor = Colors.BASIC_TEXT;
87
                    if (node.level - 1 <= LevelStorage.getLevel()) {
88
                        labelColor = getHighLightedLabelColor(node);
89
                        textColor = Colors.WHITE;
90
                    }
91
92
                    select<Element, DependencyNode>(labelElement).attr('fill', labelColor);
93
                    select<Element, DependencyNode>(textElement).style('fill', textColor);
94
                });
95
96
            zoomToHighLightedNodes();
97
        }
98
    });
99
}
100
101
export function subscribeToResetHighlight() {
102
    selectOverviewContainer().on(Subscriptions.RESET_HIGHLIGHT, () => {
103
        const highlightedNodes = selectHighLightedNodes();
104
        if (highlightedNodes.data().length) {
105
            selectAllNodes().each((node: DependencyNode) => (node.level = 0));
106
107
            const dimension = findGroupBackgroundDimension(highlightedNodes.data());
108
109
            highlightedNodes.each(function(this: SVGGElement) {
110
                const labelElement = this.firstElementChild;
111
                const textElement = this.lastElementChild;
112
113
                if (!labelElement || !textElement) {
114
                    return;
115
                }
116
117
                select<Element, DependencyNode>(labelElement).attr('fill', Colors.LIGHT_GREY);
118
                select<Element, DependencyNode>(textElement).style('fill', Colors.BASIC_TEXT);
119
            });
120
121
            hideHighlightBackground();
122
123
            centerScreenToDimension(dimension, 1);
124
        }
125
    });
126
}
127
128
export function subscribeToZoomOnArrowKey() {
129
    select('body').on(Subscriptions.ZOOM_ON_ARROW_KEY, () => {
130
        switch (event.code) {
131
            case 'ArrowUp': {
132
                const currentScaleValue = ZoomScaleStorage.getScale();
133
                const newScaleValue = currentScaleValue * ZOOM_INCREASE;
134
                if (newScaleValue <= MAXIMUM_ZOOM_SCALE) {
135
                    ZoomScaleStorage.setScale(newScaleValue);
136
                    const container = selectOverviewContainer();
137
                    container.call(() => {
138
                        return zoom<any, any>()
139
                            .on('zoom', changeZoom(ElementIds.OVERVIEW_ZOOM))
140
                            .scaleBy(container, ZOOM_INCREASE);
141
                    }, zoomIdentity);
142
                }
143
                break;
144
            }
145
            case 'ArrowDown': {
146
                const currentScaleValue = ZoomScaleStorage.getScale();
147
                const newScaleValue = currentScaleValue * ZOOM_DECREASE;
148
                if (newScaleValue >= MINIMUM_ZOOM_SCALE) {
149
                    ZoomScaleStorage.setScale(newScaleValue);
150
                    const container = selectOverviewContainer();
151
                    container.call(() => {
152
                        return zoom<any, any>()
153
                            .on('zoom', changeZoom(ElementIds.OVERVIEW_ZOOM))
154
                            .scaleBy(container, ZOOM_DECREASE);
155
                    }, zoomIdentity);
156
                }
157
                break;
158
            }
159
        }
160
    });
161
}
162
163
export function subscribeToOpenDetails(detailsNodes: TreeNode[]) {
164
    selectDetailsButtonWrapper().on(Subscriptions.OPEN_DETAILS, () => {
165
        if (selectHighLightedNodes().data().length === 0) {
166
            return;
167
        }
168
        event.stopPropagation();
169
        const selectedNode = selectAllNodes()
170
            .data()
171
            .find(node => node.level === 1);
172
        if (selectedNode) {
173
            const selectedTreeNode = detailsNodes.find(treeNode => treeNode.name === selectedNode.name);
174
            if (selectedTreeNode) {
175
                initializeDetailsView(selectedTreeNode);
176
            }
177
        }
178
    });
179
}
180
181
export function subscribeToCloseDetails() {
182
    selectDetailsExitButtonWrapper().on(Subscriptions.CLOSE_DETAILS, () => {
183
        shutdownDetailsView();
184
    });
185
}
186
187
const TOOLTIP_PADDING = 10;
188
189
export function subscribeToShowTooltipOnNodeHover() {
190
    selectAllNodes()
191
        .on(Subscriptions.SHOW_TOOLTIP, function(node) {
192
            const { x = 0, y = 0 } = node;
193
            const tooltipText = selectTooltipText();
194
            const tooltipBackground = selectTooltipBackground();
195
            selectTooltip()
196
                .transition()
197
                .duration(FAST_TRANSITION_DURATION)
198
                .style('opacity', 0.9);
199
            tooltipText
200
                .text(node.version)
201
                .attr('x', x)
202
                .attr('y', y - 25 - TOOLTIP_PADDING);
203
            const { width, height } = tooltipText.node() ? tooltipText.node()!.getBBox() : { width: 0, height: 0 };
204
            tooltipBackground
205
                .attr('x', x - TOOLTIP_PADDING)
206
                .attr('y', y - 42 - TOOLTIP_PADDING)
207
                .attr('width', width + 2 * TOOLTIP_PADDING)
208
                .attr('height', height + TOOLTIP_PADDING);
209
        })
210
        .on(Subscriptions.HIDE_TOOLTIP, function() {
211
            selectTooltip()
212
                .transition()
213
                .duration(TRANSITION_DURATION)
214
                .style('opacity', 0);
215
        });
216
}
217
218
class LevelStorage {
219
    private static level: number = 1;
220
    private static maxLevel: number = 1;
221
222
    public static getLevel(): number {
223
        return this.level;
224
    }
225
226
    public static increase() {
227
        this.level = this.level + 1;
228
    }
229
230
    public static decrease() {
231
        this.level = this.level - 1;
232
    }
233
234
    public static isBelowMax() {
235
        return this.level < this.maxLevel;
236
    }
237
238
    static isAboveMin() {
239
        return this.level > 1;
240
    }
241
242
    static setMaxLevel(maxLevel: number) {
243
        this.maxLevel = maxLevel;
244
    }
245
246
    static getMaxLevel(): number {
247
        return this.maxLevel;
248
    }
249
250
    public static reset() {
251
        this.level = 1;
252
        this.maxLevel = 1;
253
    }
254
}
255
256
export class ZoomScaleStorage {
257
    private static currentScale = 1;
258
259
    public static setScale(newScale: number) {
260
        this.currentScale = newScale;
261
    }
262
263
    public static getScale() {
264
        return this.currentScale;
265
    }
266
}
267